﻿// Copyright (c) 2013, Simon Carter.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted 
// provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this list of 
//   conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice, this list of 
//  conditions and the following disclaimer in the documentation and/or other materials provided 
//  with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// History
// Initial Release - Simon Carter
// 18/10/2013 - Simon Carter - Update to have params for install/uninstall and run as application

using System;
using System.Collections.Generic;
using System.ServiceProcess;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Globalization;
using System.Configuration.Install;
using System.Reflection;
using System.Windows.Forms;

using TCPMessageServer;
using ErrorManager.Server.DAL;

namespace ErrorManager.Server
{
    public class ErrorServer : System.ServiceProcess.ServiceBase
    {
        #region Constants

        private static string ERROR_MANAGER_SERVICE_NAME = "Error Manager";

        #endregion Constants

        #region Private Members

        private MessageServer _messageServer;

        /// <summary> 
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.Container components = null;

        #endregion Private Members

        #region Static Initialization

        /// <summary>
        /// Initialization Method
        /// </summary>
        /// <param name="args">args</param>
        static void Main(string[] args)
        {

            System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo("en-GB");

            System.ServiceProcess.ServiceBase[] ServicesToRun;

            if (System.Environment.UserInteractive)
            {
                string parameter = string.Concat(args);

                try
                {
                    switch (parameter)
                    {
                        case "/i": // install
                            if (!ServiceInstalled(ERROR_MANAGER_SERVICE_NAME))
                                ManagedInstallerClass.InstallHelper(new string[] { Assembly.GetExecutingAssembly().Location });

                            break;

                        case "/u": // uninstall
                            if (ServiceRunning(ERROR_MANAGER_SERVICE_NAME))
                                StopService(ERROR_MANAGER_SERVICE_NAME);

                            ManagedInstallerClass.InstallHelper(new string[] { "/u", Assembly.GetExecutingAssembly().Location });

                            break;

                        case "/start": // start
                            if (!ServiceRunning(ERROR_MANAGER_SERVICE_NAME))
                                StartService(ERROR_MANAGER_SERVICE_NAME);

                            break;

                        case "/stop": // stop
                            if (ServiceRunning(ERROR_MANAGER_SERVICE_NAME))
                                StopService(ERROR_MANAGER_SERVICE_NAME);

                            break;

                        default:
                            ErrorServer server = new ErrorServer();
                            server.InitializeTCPServer();

                            break;
                    }
                }
                catch (Exception err)
                {
                    MessageBox.Show(String.Format("{0}\r\n{1}", err.Message, err.StackTrace.ToString()));
                    ErrorServer logserver = new ErrorServer();
                    logserver.AddToLogFile(err.Message);
                    logserver.AddToLogFile(err.StackTrace.ToString());
                    logserver = null;
                }
            }
            else
            {
                ServicesToRun = new System.ServiceProcess.ServiceBase[] { new ErrorServer() };
                System.ServiceProcess.ServiceBase.Run(ServicesToRun);
            }
        }

        /// <summary>
        /// Starts a service
        /// </summary>
        /// <param name="ServiceName">Name of service to stop</param>
        private static void StartService(string ServiceName)
        {
            ServiceController[] services = ServiceController.GetServices();

            foreach (ServiceController service in services)
            {
                if (service.ServiceName == ServiceName)
                {
                    try
                    {
                        service.Start();
                    }
                    catch (Exception err)
                    {
                        // not possible permissions etc, may need to run as administrator
                        MessageBox.Show(err.Message, "Start Service");
                    } 

                    break;
                }
            }
        }

        /// <summary>
        /// Stops a windows service
        /// </summary>
        /// <param name="ServiceName">Name of service to stop</param>
        private static void StopService(string ServiceName)
        {
            ServiceController[] services = ServiceController.GetServices();

            foreach (ServiceController service in services)
            {
                if (service.ServiceName == ServiceName)
                {
                    try
                    {
                        service.Stop();
                    }
                    catch (Exception err)
                    {
                        //not possible permissions etc may need to run as administrator
                        MessageBox.Show(err.Message, "Stop Service");
                    }

                    break;
                }
            }
        }

        /// <summary>
        /// Determines wether a service is installed
        /// </summary>
        /// <param name="ServiceName">Name of Service</param>
        /// <returns>true if service installed, otherwise false</returns>
        private static bool ServiceInstalled(string ServiceName)
        {
            bool Result = false;

            ServiceController[] services = ServiceController.GetServices();

            foreach (ServiceController service in services)
            {
                if (service.ServiceName == ServiceName)
                {
                    Result = true;
                    break;
                }
            }

            return (Result);
        }

        /// <summary>
        /// Determines wehter a service is running
        /// </summary>
        /// <param name="ServiceName">Name of Service</param>
        /// <returns>true if running, otherwise false</returns>
        private static bool ServiceRunning(string ServiceName)
        {
            bool Result = false;

            ServiceController[] services = ServiceController.GetServices();

            foreach (ServiceController service in services)
            {
                if (service.ServiceName == ServiceName)
                {
                    Result = service.Status == ServiceControllerStatus.Running;
                    break;
                }
            }

            return (Result);
        }

        #endregion Static Initialization

        #region Constructors

        public ErrorServer()
        {
            // This call is required by the Windows.Forms Component Designer.
            InitializeComponent();
        }

        #endregion Constructors

        #region Private Methods

        /// <summary> 
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            // 
            // ErrorServer
            // 
            this.AutoLog = false;
            this.ServiceName = ERROR_MANAGER_SERVICE_NAME;
        }

        #endregion Private Methods

        #region Properties

        internal string Path
        {
            get
            {
                string path = System.Reflection.Assembly.GetExecutingAssembly().CodeBase;
                path = System.IO.Path.GetDirectoryName(path);
                path = path.Substring(6);

                return (path);
            }
        }

        #endregion Properties

        #region Logging

        internal void AddToLogFile(string logCommand, string logParams = "")
        {
            try
            {
                string LogPath = Path + "\\Logs\\";

                if (!Directory.Exists(LogPath))
                    Directory.CreateDirectory(LogPath);

                StreamWriter w = File.AppendText(LogPath + String.Format("{0}.txt", DateTime.Now.ToString("ddMMyyyy")));
                try
                {
                    w.WriteLine("{0},{1},{2}", DateTime.Now.ToString("hh:mm:ss"), logCommand, logParams);

                    if (logParams.Contains("\n"))
                    {
                        string[] parms = logParams.Split('\n');

                        //1 line for each line in the parameter
                        foreach (string s in parms)
                        {
                            w.WriteLine("{0},{1},{2}", DateTime.Now.ToString("hh:mm:ss"), logCommand, s);
                        }
                    }
                }
                finally
                {
                    // Update the underlying file.
                    w.Flush();

                    w.Close();
                    w = null;
                }
            }
            catch (Exception err)
            {
                string s = "";
                s = err.Message;
            }
        }

        #endregion Logging

        #region Overridden Service Methods

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose(disposing);
        }

        /// <summary>
        /// Set things in motion so your service can do its work.
        /// </summary>
        protected override void OnStart(string[] args)
        {
            AddToLogFile("Start");
            InitializeTCPServer();
        }

        /// <summary>
        /// Stop this service.
        /// </summary>
        protected override void OnStop()
        {
            AddToLogFile("Stop");
            _messageServer.Stop();
        }

        protected override void OnContinue()
        {
            AddToLogFile("Continue");
        }

        #endregion Overridden Service Methods

        #region TCP Message Server

        private void InitializeTCPServer()
        {
            _messageServer = new MessageServer(EngineHelper.GetEngine().BindIPPort());
            _messageServer.MessageReceived += _messageServer_MessageReceived;
            _messageServer.ClientConnected += _messageServer_ClientConnected;
            _messageServer.ClientDisconnected += _messageServer_ClientDisconnected;
            _messageServer.OnError += _messageServer_OnError;
            _messageServer.Started += _messageServer_Started;
            _messageServer.Stopped += _messageServer_Stopped;
            _messageServer.ClientLogin += _messageServer_ClientLogin;
            _messageServer.AllowClientConnect += _messageServer_AllowClientConnect;
            _messageServer.FileReceive += _messageServer_FileReceive;
            _messageServer.FileReceived += _messageServer_FileReceived;
            _messageServer.AcceptFiles = true;
            _messageServer.ClientTimeOut = 1000;
            _messageServer.LoginRequird = true;

            //start the server
            _messageServer.Start();
        }

        void _messageServer_FileReceived(object sender, TransferFileEventArgs e)
        {
            if (e.Received == e.Total)
            {
                AddToLogFile("RECEIVED_FILE", String.Format("User: {0}; File: {1}", _messageServer.UserName(e.ClientID), e.FileName));
                AddToLogFile("FILE_TRANSFER_SPEED", String.Format("{0}", e.TransferSpeed));
                AddToLogFile("FILE_TRANSFER_TIME", String.Format("{0} seconds", e.TimeTaken.TotalSeconds));
            }
        }

        void _messageServer_FileReceive(object sender, TransferFileEventArgs e)
        {
            //will store files in [Rootpath]\[UserName]\Name of file
            object userData = _messageServer.UserData(e.ClientID);

            if (userData == null) // console client
                e.FileName = String.Format("{0}\\{1}\\{2}", EngineHelper.Path, _messageServer.UserName(e.ClientID), e.FileName);
            else // error client
                e.FileName = String.Format("{0}\\{1}\\{2}_{3}", EngineHelper.Path, _messageServer.UserName(e.ClientID), userData, e.FileName);

            if (!Directory.Exists(System.IO.Path.GetDirectoryName(e.FileName)))
                Directory.CreateDirectory(System.IO.Path.GetDirectoryName(e.FileName));

            AddToLogFile("RECEIVE_FILE", String.Format("User: {0}; File: {1}", _messageServer.UserName(e.ClientID), e.FileName));
        }

        void _messageServer_ClientLogin(object sender, ClientLoginArgs e)
        {
            //trying to login
            AddToLogFile("LOGIN", String.Format("User: {0}; IP Address: {1}", e.Username, e.IPAddress));

            //todo add login checks here
            e.LoggedIn = EngineHelper.GetEngine().ClientLogin(e.Username, e.Password);

            if (e.LoggedIn)
            {
                AddToLogFile("LOGIN_SUCCESS", String.Format("User: {0}; IP Address {1}", e.Username, e.IPAddress));
            }
            else
            {
                AddToLogFile("LOGIN_FAIL", String.Format("User: {0}; IP Address {1}", e.Username, e.IPAddress));
            }
        }

        void _messageServer_AllowClientConnect(object sender, ClientAllowConnectEventArgs e)
        {
            AddToLogFile("ALLOW_CLIENT_CONNECT", e.IPAddress);
            e.Allow = EngineHelper.GetEngine().ClientAllowConnect(e.IPAddress);

            if (e.Allow)
                AddToLogFile("CLIENT_CONNECT_SUCCESS", e.IPAddress);
            else
                AddToLogFile("CLIENT_CONNECT_FAIL", e.IPAddress);
        }

        void _messageServer_Stopped(object sender, EventArgs e)
        {
            AddToLogFile("TCP Server Stopped");
        }

        void _messageServer_Started(object sender, EventArgs e)
        {
            AddToLogFile("TCP Server Started");
        }

        void _messageServer_OnError(object sender, TCPMessageServer.ErrorEventArgs e)
        {
            AddToLogFile("ERROR", e.Error.Message);

            if (e.Error.StackTrace != null)
                AddToLogFile(e.Error.StackTrace.ToString());

            e.Continue = true;
        }

        void _messageServer_ClientDisconnected(object sender, ClientEventArgs e)
        {
            AddToLogFile("CLIENT_DISCONNECTED", String.Format("{1} - {0}", e.ClientID, e.IPAddress));
        }

        void _messageServer_ClientConnected(object sender, ClientEventArgs e)
        {
            AddToLogFile("CLIENT_CONNECTED", String.Format("{1} -  {0}", e.ClientID, e.IPAddress));
        }

        void _messageServer_MessageReceived(object sender, TCPMessageServer.Message message)
        {
            switch (message.Type)
            {
                case MessageType.Command:
                    ProcessCommand(message);
                    
                    break;
                case MessageType.Error:

                    string fileName = String.Empty;

                    switch (message.Title)
                    {
                        case "Error Information":
                            _messageServer.UserData(message.ClientID, EngineHelper.GetEngine().ClientErrorUniqueID(_messageServer.UserName(message.ClientID)));

                            fileName = String.Format("{0}\\{1}\\{2}.dat", EngineHelper.Path, _messageServer.UserName(message.ClientID),
                                _messageServer.UserData(message.ClientID));

                            break;
                        case "System Information":
                            fileName = String.Format("{0}\\{1}\\{2}.system.dat", EngineHelper.Path, _messageServer.UserName(message.ClientID),
                               _messageServer.UserData(message.ClientID));

                            break;
                        case "Extra Information":
                            fileName = String.Format("{0}\\{1}\\{2}.extra.dat", EngineHelper.Path, _messageServer.UserName(message.ClientID),
                               _messageServer.UserData(message.ClientID));

                            break;
                    }

                    if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(fileName)))
                        System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(fileName));


                    //save the error information
                    System.IO.StreamWriter file = new System.IO.StreamWriter(fileName);
                    file.WriteLine(message.Contents);
                    file.Close();

                    break;
                case MessageType.Acknowledge:
                    _messageServer.SendMessage(message, false);

                    break;
            }

        }

        private void ProcessCommand(TCPMessageServer.Message message)
        {
            string[] parameters;

            switch (message.Title)
            {
                case "GET_USERS": // list of users
                    message.Contents = EngineHelper.GetEngine().UsersGet(_messageServer.UserName(message.ClientID));
                    _messageServer.SendMessage(message, false);

                    break;
                case "PERMISSION": // permissions
                    parameters = message.Contents.Split('$');
                    UserSecurityPermissions permission = (UserSecurityPermissions)Enum.Parse(typeof(UserSecurityPermissions), parameters[1], true);
                    message.Contents += EngineHelper.GetEngine().UserHasPermission(_messageServer.UserName(message.ClientID), permission).ToString();
                    _messageServer.SendMessage(message, false);

                    break;
                case "SERVER_PORT": // service port
                    message.Contents = EngineHelper.GetEngine().ServerConfigGetPort(_messageServer.UserName(message.ClientID)).ToString();
                    _messageServer.SendMessage(message, false);

                    break;

                case "SERVER_DAL": // Data access layer
                    message.Contents = EngineHelper.GetEngine().ServerConfigGetDAL(_messageServer.UserName(message.ClientID)).ToString();
                    _messageServer.SendMessage(message, false);

                    break;

                case "USER_ERROR_COUNT": // error count
                    message.Contents = EngineHelper.GetEngine().ClientErrorReportCount(_messageServer.UserName(message.ClientID)).ToString();
                    _messageServer.SendMessage(message, false);

                    break;

                case "USER_ERRORS_GET": // list of user errors
                    message.Contents = EngineHelper.GetEngine().ClientErrorsGet(_messageServer.UserName(message.ClientID));
                    _messageServer.SendMessage(message, false);

                    break;

                case "USER_ERROR_SEND_EXTRA_ERROR_DATA":
                    message.Contents = EngineHelper.GetEngine().UserGetErrorExtraInformation(_messageServer.UserName(message.ClientID), Convert.ToUInt32(message.Contents));
                    _messageServer.SendMessage(message, false);

                    break;

                case "USER_ERROR_SEND_ERROR_DATA":
                    message.Contents = EngineHelper.GetEngine().UserGetErrorInformation(_messageServer.UserName(message.ClientID), Convert.ToUInt32(message.Contents));
                    _messageServer.SendMessage(message, false);

                    break;

                case "USER_ERROR_HAS_IMAGE":
                    message.Contents = EngineHelper.GetEngine().UserGetErrorHasImage(_messageServer.UserName(message.ClientID), Convert.ToUInt32(message.Contents)).ToString();
                    _messageServer.SendMessage(message, false);

                    break;

                case "USER_ERROR_GET_IMAGE":
                    message.Contents = EngineHelper.GetEngine().UserGetErrorImage(_messageServer.UserName(message.ClientID), Convert.ToUInt32(message.Contents)).ToString();
                    _messageServer.SendMessage(message, false);

                    break;

                case "USER_ERROR_DELETE":
                    EngineHelper.GetEngine().UserErrorDelete(_messageServer.UserName(message.ClientID), Convert.ToUInt32(message.Contents));
                    message.Type = MessageType.Acknowledge;
                    _messageServer.SendMessage(message, false);

                    break;

                case "USER_ERROR_IGNORE":
                    EngineHelper.GetEngine().UserErrorIgnore(_messageServer.UserName(message.ClientID), Convert.ToUInt32(message.Contents));
                    message.Type = MessageType.Acknowledge;
                    _messageServer.SendMessage(message, false);

                    break;

                case "GET_USER_PASSWORD":
                    message.Contents = EngineHelper.GetEngine().UserGetPassword(_messageServer.UserName(message.ClientID), message.Contents).ToString();
                    _messageServer.SendMessage(message, false);

                    break;

                case "GET_USER_ACTIVE":
                    message.Contents = EngineHelper.GetEngine().UserGetActive(_messageServer.UserName(message.ClientID), message.Contents).ToString();
                    _messageServer.SendMessage(message, false);

                    break;

                case "GET_USER_PERMISSIONS":
                    message.Contents = EngineHelper.GetEngine().UserGetPermissions(_messageServer.UserName(message.ClientID), message.Contents).ToString();
                    _messageServer.SendMessage(message, false);

                    break;

                case "GET_USER_LAST_LOGIN":
                    message.Contents = EngineHelper.GetEngine().UserGetLastLogin(_messageServer.UserName(message.ClientID), message.Contents).ToString();
                    _messageServer.SendMessage(message, false);

                    break;

                case "DELETE_USER":
                    message.Contents = EngineHelper.GetEngine().UserDelete(_messageServer.UserName(message.ClientID), message.Contents).ToString();
                    _messageServer.SendMessage(message, false);

                    break;

                case "USER_CREATE":
                    message.Contents = EngineHelper.GetEngine().UserCreate(_messageServer.UserName(message.ClientID), message.Contents).ToString();
                    _messageServer.SendMessage(message, false);

                    break;

                case "SET_USER_PASSWORD":
                    parameters = message.Contents.Split('$');
                    message.Contents = Convert.ToString(EngineHelper.GetEngine().UserSetPassword(_messageServer.UserName(message.ClientID), parameters[0], parameters[1]));

                    break;

                case "SET_USER_ACTIVE":
                    parameters = message.Contents.Split('$');
                    message.Contents = Convert.ToString(EngineHelper.GetEngine().UserSetActive(_messageServer.UserName(message.ClientID), parameters[0], Convert.ToBoolean(parameters[1])));

                    break;

                case "SET_USER_PERMISSIONS":
                    parameters = message.Contents.Split('$');
                    message.Contents = Convert.ToString(EngineHelper.GetEngine().UserSetPermissions(_messageServer.UserName(message.ClientID), parameters[0], (UserSecurityPermissions)Convert.ToInt32(parameters[1])));

                    break;
            }
        }
        #endregion TCP Message Server
    }
}
